<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Plugins\ECRecover;

use Exception;
use kornrunner\Keccak;
use Srv\Libs\Plugins\ECRecover\CryptoCurrency\Signature;

class ECRecover
{
    /**
     * @param string $msg
     * @param string $signed
     * @param string $address
     * @return bool
     * isPersonalSig
     */
    public static function isPersonalSig(string $msg, string $signed, string $address):bool
    {
        if(strlen($msg) < 1 || strlen($signed) < 32 || strlen(trim($address)) !== 42) return false;
        return strtolower(self::personalECRecover($msg, $signed)) === strtolower(trim($address));
    }

    /**
     * @param string $msg
     * @param string $signed
     * @return string
     * personalECRecover
     */
    public static function personalECRecover(string $msg, string $signed):string
    {
        try{
            $personalMsg    = "\x19Ethereum Signed Message:\n".strlen($msg).$msg;
            $hex            = self::keccak256($personalMsg);
            return self::ecRecover($hex, $signed);
        }catch(Exception $Exception){
            return '';
        }
    }

    /**
     * @param string $hex
     * @param string $signed
     * @return string
     * @throws Exception
     * ecRecover
     */
    private static function ecRecover(string $hex, string $signed):string
    {
        $rHex               = substr($signed, 2, 64);
        $sHex               = substr($signed, 66, 64);
        $vValue             = hexdec(substr($signed, 130, 2));
        $messageHex         = substr($hex, 2);
        $messageByteArray   = unpack('C*', hex2bin($messageHex));
        $messageGmp         = gmp_init("0x" . $messageHex);
        $r                  = $rHex;		//hex string without 0x
        $s                  = $sHex; 	//hex string without 0x
        $v                  = $vValue; 	//27 or 28
        $rByteArray         = unpack('C*', hex2bin($r));
        $sByteArray         = unpack('C*', hex2bin($s));
        $rGmp               = gmp_init("0x" . $r);
        $sGmp               = gmp_init("0x" . $s);
        if($v !== 27 && $v !== 28) $v += 27;
        $recovery           = $v - 27;
        if($recovery !== 0 && $recovery !== 1){
            throw new Exception('Invalid signature v value');
        }
        $publicKey          = Signature::recoverPublicKey($rGmp, $sGmp, $messageGmp, $recovery);
        $publicKeyString    = $publicKey['x'] . $publicKey['y'];
        return '0x'. substr(self::keccak256(hex2bin($publicKeyString)), -40);
    }

    /**
     * @param string $str
     * @return string
     * @throws Exception
     * keccak256
     */
    private static function keccak256(string $str):string
    {
        return '0x'. Keccak::hash($str, 256);
    }
}